Merge branch 'jc/maint-log-grep-all-match'
authorJunio C Hamano <gitster@pobox.com>
Tue, 18 Sep 2012 21:37:53 +0000 (14:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 18 Sep 2012 21:37:54 +0000 (14:37 -0700)
Fix a long-standing bug in "git log --grep" when multiple "--grep"
are used together with "--all-match" and "--author" or "--committer".

* jc/maint-log-grep-all-match:
t7810-grep: test --all-match with multiple --grep and --author options
t7810-grep: test interaction of multiple --grep and --author options
t7810-grep: test multiple --author with --all-match
t7810-grep: test multiple --grep with and without --all-match
t7810-grep: bring log --grep tests in common form
grep.c: mark private file-scope symbols as static
log: document use of multiple commit limiting options
log --grep/--author: honor --all-match honored for multiple --grep patterns
grep: show --debug output only once
grep: teach --debug option to dump the parse tree

1  2 
Documentation/rev-list-options.txt
builtin/grep.c
grep.h
revision.c
t/t7810-grep.sh
index 918c1109f263c667264c9961c118d549795eab9d,c828408a80f8f73fe1aa84cc9b30c46d063c2425..1fc2a18404bd34a2e1ac9f5fe21d64c1827f13a6
@@@ -3,13 -3,19 +3,20 @@@ Commit Limitin
  
  Besides specifying a range of commits that should be listed using the
  special notations explained in the description, additional commit
- limiting may be applied. Note that they are applied before commit
- ordering and formatting options, such as '--reverse'.
+ limiting may be applied.
+ Using more options generally further limits the output (e.g.
+ `--since=<date1>` limits to commits newer than `<date1>`, and using it
+ with `--grep=<pattern>` further limits to commits whose log message
+ has a line that matches `<pattern>`), unless otherwise noted.
+ Note that these are applied before commit
+ ordering and formatting options, such as `--reverse`.
  
  --
  
 --n 'number'::
 +-<number>::
 +-n <number>::
  --max-count=<number>::
  
        Limit the number of commits to output.
@@@ -39,16 -45,22 +46,22 @@@ endif::git-rev-list[
  --committer=<pattern>::
  
        Limit the commits output to ones with author/committer
-       header lines that match the specified pattern (regular expression).
+       header lines that match the specified pattern (regular
+       expression).  With more than one `--author=<pattern>`,
+       commits whose author matches any of the given patterns are
+       chosen (similarly for multiple `--committer=<pattern>`).
  
  --grep=<pattern>::
  
        Limit the commits output to ones with log message that
-       matches the specified pattern (regular expression).
+       matches the specified pattern (regular expression).  With
+       more than one `--grep=<pattern>`, commits whose message
+       matches any of the given patterns are chosen (but see
+       `--all-match`).
  
  --all-match::
        Limit the commits output to ones that match all given --grep,
-       --author and --committer instead of ones that match at least one.
+       instead of ones that match at least one.
  
  -i::
  --regexp-ignore-case::
@@@ -579,33 -591,16 +592,33 @@@ Commit Orderin
  
  By default, the commits are shown in reverse chronological order.
  
 ---topo-order::
 +--date-order::
 +      Show no parents before all of its children are shown, but
 +      otherwise show commits in the commit timestamp order.
  
 -      This option makes them appear in topological order (i.e.
 -      descendant commits are shown before their parents).
 +--topo-order::
 +      Show no parents before all of its children are shown, and
 +      avoid showing commits on multiple lines of history
 +      intermixed.
 ++
 +For example, in a commit history like this:
 ++
 +----------------------------------------------------------------
  
 ---date-order::
 +    ---1----2----4----7
 +      \              \
 +       3----5----6----8---
  
 -      This option is similar to '--topo-order' in the sense that no
 -      parent comes before all of its children, but otherwise things
 -      are still ordered in the commit timestamp order.
 +----------------------------------------------------------------
 ++
 +where the numbers denote the order of commit timestamps, `git
 +rev-list` and friends with `--date-order` show the commits in the
 +timestamp order: 8 7 6 5 4 3 2 1.
 ++
 +With `--topo-order`, they would show 8 6 5 3 7 4 2 1 (or 8 7 4 2 6 5
 +3 1); some older commits are shown before newer ones in order to
 +avoid showing the commits from two parallel development track mixed
 +together.
  
  --reverse::
  
@@@ -637,14 -632,9 +650,14 @@@ These options are mostly targeted for p
        Only useful with '--objects'; print the object IDs that are not
        in packs.
  
 ---no-walk::
 +--no-walk[=(sorted|unsorted)]::
  
 -      Only show the given revs, but do not traverse their ancestors.
 +      Only show the given commits, but do not traverse their ancestors.
 +      This has no effect if a range is specified. If the argument
 +      "unsorted" is given, the commits are show in the order they were
 +      given on the command line. Otherwise (if "sorted" or no argument
 +      was given), the commits are show in reverse chronological order
 +      by commit time.
  
  --do-walk::
  
@@@ -782,7 -772,7 +795,7 @@@ options may be given. See linkgit:git-d
  
  --cc::
  
 -      This flag implies the '-c' options and further compresses the
 +      This flag implies the '-c' option and further compresses the
        patch output by omitting uninteresting hunks whose contents in
        the parents have only two variants and the merge result picks
        one of them without modification.
diff --combined builtin/grep.c
index 09ca4c980e633895dd104307e76770da3f208a06,a7e8df0d409fd431b71d1458cde6252ae119bf18..82530a61b483738382c810bcb141e33b06729d6f
@@@ -19,7 -19,7 +19,7 @@@
  #include "dir.h"
  
  static char const * const grep_usage[] = {
 -      "git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
 +      N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
        NULL
  };
  
@@@ -209,6 -209,7 +209,7 @@@ static void start_threads(struct grep_o
                int err;
                struct grep_opt *o = grep_opt_dup(opt);
                o->output = strbuf_out;
+               o->debug = 0;
                compile_grep_patterns(o);
                err = pthread_create(&threads[i], NULL, run, o);
  
@@@ -260,53 -261,6 +261,53 @@@ static int wait_all(void
  }
  #endif
  
 +static int parse_pattern_type_arg(const char *opt, const char *arg)
 +{
 +      if (!strcmp(arg, "default"))
 +              return GREP_PATTERN_TYPE_UNSPECIFIED;
 +      else if (!strcmp(arg, "basic"))
 +              return GREP_PATTERN_TYPE_BRE;
 +      else if (!strcmp(arg, "extended"))
 +              return GREP_PATTERN_TYPE_ERE;
 +      else if (!strcmp(arg, "fixed"))
 +              return GREP_PATTERN_TYPE_FIXED;
 +      else if (!strcmp(arg, "perl"))
 +              return GREP_PATTERN_TYPE_PCRE;
 +      die("bad %s argument: %s", opt, arg);
 +}
 +
 +static void grep_pattern_type_options(const int pattern_type, struct grep_opt *opt)
 +{
 +      switch (pattern_type) {
 +      case GREP_PATTERN_TYPE_UNSPECIFIED:
 +              /* fall through */
 +
 +      case GREP_PATTERN_TYPE_BRE:
 +              opt->fixed = 0;
 +              opt->pcre = 0;
 +              opt->regflags &= ~REG_EXTENDED;
 +              break;
 +
 +      case GREP_PATTERN_TYPE_ERE:
 +              opt->fixed = 0;
 +              opt->pcre = 0;
 +              opt->regflags |= REG_EXTENDED;
 +              break;
 +
 +      case GREP_PATTERN_TYPE_FIXED:
 +              opt->fixed = 1;
 +              opt->pcre = 0;
 +              opt->regflags &= ~REG_EXTENDED;
 +              break;
 +
 +      case GREP_PATTERN_TYPE_PCRE:
 +              opt->fixed = 0;
 +              opt->pcre = 1;
 +              opt->regflags &= ~REG_EXTENDED;
 +              break;
 +      }
 +}
 +
  static int grep_config(const char *var, const char *value, void *cb)
  {
        struct grep_opt *opt = cb;
  
        if (!strcmp(var, "grep.extendedregexp")) {
                if (git_config_bool(var, value))
 -                      opt->regflags |= REG_EXTENDED;
 +                      opt->extended_regexp_option = 1;
                else
 -                      opt->regflags &= ~REG_EXTENDED;
 +                      opt->extended_regexp_option = 0;
                return 0;
        }
  
 +      if (!strcmp(var, "grep.patterntype")) {
 +              opt->pattern_type_option = parse_pattern_type_arg(var, value);
 +              return 0;
 +  }
 +
        if (!strcmp(var, "grep.linenumber")) {
                opt->linenum = git_config_bool(var, value);
                return 0;
@@@ -721,88 -670,95 +722,88 @@@ int cmd_grep(int argc, const char **arg
        int i;
        int dummy;
        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;
 +      int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
  
        struct option options[] = {
                OPT_BOOLEAN(0, "cached", &cached,
 -                      "search in index instead of in the work tree"),
 +                      N_("search in index instead of in the work tree")),
                OPT_NEGBIT(0, "no-index", &use_index,
 -                       "finds in contents not managed by git", 1),
 +                       N_("find in contents not managed by git"), 1),
                OPT_BOOLEAN(0, "untracked", &untracked,
 -                      "search in both tracked and untracked files"),
 +                      N_("search in both tracked and untracked files")),
                OPT_SET_INT(0, "exclude-standard", &opt_exclude,
 -                          "search also in ignored files", 1),
 +                          N_("search also in ignored files"), 1),
                OPT_GROUP(""),
                OPT_BOOLEAN('v', "invert-match", &opt.invert,
 -                      "show non-matching lines"),
 +                      N_("show non-matching lines")),
                OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
 -                      "case insensitive matching"),
 +                      N_("case insensitive matching")),
                OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
 -                      "match patterns only at word boundaries"),
 +                      N_("match patterns only at word boundaries")),
                OPT_SET_INT('a', "text", &opt.binary,
 -                      "process binary files as text", GREP_BINARY_TEXT),
 +                      N_("process binary files as text"), GREP_BINARY_TEXT),
                OPT_SET_INT('I', NULL, &opt.binary,
 -                      "don't match patterns in binary files",
 +                      N_("don't match patterns in binary files"),
                        GREP_BINARY_NOMATCH),
 -              { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, "depth",
 -                      "descend at most <depth> levels", PARSE_OPT_NONEG,
 +              { OPTION_INTEGER, 0, "max-depth", &opt.max_depth, N_("depth"),
 +                      N_("descend at most <depth> levels"), PARSE_OPT_NONEG,
                        NULL, 1 },
                OPT_GROUP(""),
 -              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_SET_INT('E', "extended-regexp", &pattern_type_arg,
 +                          N_("use extended POSIX regular expressions"),
 +                          GREP_PATTERN_TYPE_ERE),
 +              OPT_SET_INT('G', "basic-regexp", &pattern_type_arg,
 +                          N_("use basic POSIX regular expressions (default)"),
 +                          GREP_PATTERN_TYPE_BRE),
 +              OPT_SET_INT('F', "fixed-strings", &pattern_type_arg,
 +                          N_("interpret patterns as fixed strings"),
 +                          GREP_PATTERN_TYPE_FIXED),
 +              OPT_SET_INT('P', "perl-regexp", &pattern_type_arg,
 +                          N_("use Perl-compatible regular expressions"),
 +                          GREP_PATTERN_TYPE_PCRE),
                OPT_GROUP(""),
 -              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_BOOLEAN('n', "line-number", &opt.linenum, N_("show line numbers")),
 +              OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1),
 +              OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1),
                OPT_NEGBIT(0, "full-name", &opt.relative,
 -                      "show filenames relative to top directory", 1),
 +                      N_("show filenames relative to top directory"), 1),
                OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
 -                      "show only filenames instead of matching lines"),
 +                      N_("show only filenames instead of matching lines")),
                OPT_BOOLEAN(0, "name-only", &opt.name_only,
 -                      "synonym for --files-with-matches"),
 +                      N_("synonym for --files-with-matches")),
                OPT_BOOLEAN('L', "files-without-match",
                        &opt.unmatch_name_only,
 -                      "show only the names of files without match"),
 +                      N_("show only the names of files without match")),
                OPT_BOOLEAN('z', "null", &opt.null_following_name,
 -                      "print NUL after filenames"),
 +                      N_("print NUL after filenames")),
                OPT_BOOLEAN('c', "count", &opt.count,
 -                      "show the number of matches instead of matching lines"),
 -              OPT__COLOR(&opt.color, "highlight matches"),
 +                      N_("show the number of matches instead of matching lines")),
 +              OPT__COLOR(&opt.color, N_("highlight matches")),
                OPT_BOOLEAN(0, "break", &opt.file_break,
 -                      "print empty line between matches from different files"),
 +                      N_("print empty line between matches from different files")),
                OPT_BOOLEAN(0, "heading", &opt.heading,
 -                      "show filename only once above matches from same file"),
 +                      N_("show filename only once above matches from same file")),
                OPT_GROUP(""),
 -              OPT_CALLBACK('C', "context", &opt, "n",
 -                      "show <n> context lines before and after matches",
 +              OPT_CALLBACK('C', "context", &opt, N_("n"),
 +                      N_("show <n> context lines before and after matches"),
                        context_callback),
                OPT_INTEGER('B', "before-context", &opt.pre_context,
 -                      "show <n> context lines before matches"),
 +                      N_("show <n> context lines before matches")),
                OPT_INTEGER('A', "after-context", &opt.post_context,
 -                      "show <n> context lines after matches"),
 -              OPT_NUMBER_CALLBACK(&opt, "shortcut for -C NUM",
 +                      N_("show <n> context lines after matches")),
 +              OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"),
                        context_callback),
                OPT_BOOLEAN('p', "show-function", &opt.funcname,
 -                      "show a line with the function name before matches"),
 +                      N_("show a line with the function name before matches")),
                OPT_BOOLEAN('W', "function-context", &opt.funcbody,
 -                      "show the surrounding function"),
 +                      N_("show the surrounding function")),
                OPT_GROUP(""),
 -              OPT_CALLBACK('f', NULL, &opt, "file",
 -                      "read patterns from file", file_callback),
 -              { OPTION_CALLBACK, 'e', NULL, &opt, "pattern",
 -                      "match <pattern>", PARSE_OPT_NONEG, pattern_callback },
 +              OPT_CALLBACK('f', NULL, &opt, N_("file"),
 +                      N_("read patterns from file"), file_callback),
 +              { OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"),
 +                      N_("match <pattern>"), PARSE_OPT_NONEG, pattern_callback },
                { OPTION_CALLBACK, 0, "and", &opt, NULL,
 -                "combine patterns specified with -e",
 +                N_("combine patterns specified with -e"),
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
                OPT_BOOLEAN(0, "or", &dummy, ""),
                { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH,
                  close_callback },
                OPT__QUIET(&opt.status_only,
 -                         "indicate hit with exit status without output"),
 +                         N_("indicate hit with exit status without output")),
                OPT_BOOLEAN(0, "all-match", &opt.all_match,
 -                      "show only matches from files that match all patterns"),
 +                      N_("show only matches from files that match all patterns")),
+               { OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
 -                "show parse tree for grep expression",
++                N_("show parse tree for grep expression"),
+                 PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1 },
                OPT_GROUP(""),
                { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
 -                      "pager", "show matching files in the pager",
 +                      N_("pager"), N_("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",
 +                          N_("allow calling of grep(1) (ignored by this build)")),
 +              { OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"),
                  PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
                OPT_END()
        };
        opt.header_tail = &opt.header_list;
        opt.regflags = REG_NEWLINE;
        opt.max_depth = -1;
 +      opt.pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED;
 +      opt.extended_regexp_option = 0;
  
        strcpy(opt.color_context, "");
        strcpy(opt.color_filename, "");
                             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 (pattern_type_arg != GREP_PATTERN_TYPE_UNSPECIFIED)
 +              grep_pattern_type_options(pattern_type_arg, &opt);
 +      else if (opt.pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED)
 +              grep_pattern_type_options(opt.pattern_type_option, &opt);
 +      else if (opt.extended_regexp_option)
 +              grep_pattern_type_options(GREP_PATTERN_TYPE_ERE, &opt);
  
        if (use_index && !startup_info->have_repository)
                /* die the same way as if we did it at the beginning */
        if (!seen_dashdash) {
                int j;
                for (j = i; j < argc; j++)
 -                      verify_filename(prefix, argv[j]);
 +                      verify_filename(prefix, argv[j], j == i);
        }
  
        paths = get_pathspec(prefix, argv + i);
diff --combined grep.h
index 75afb7b10564a58f6427a03abccfb334facfea3b,d66b19712c18cb93bf789c37ed36ad787fa89a92..8a28a676fc3b16a0857e3f4b80824d929b6b444d
--- 1/grep.h
--- 2/grep.h
+++ b/grep.h
@@@ -58,14 -58,6 +58,14 @@@ enum grep_expr_node 
        GREP_NODE_OR
  };
  
 +enum grep_pattern_type {
 +      GREP_PATTERN_TYPE_UNSPECIFIED = 0,
 +      GREP_PATTERN_TYPE_BRE,
 +      GREP_PATTERN_TYPE_ERE,
 +      GREP_PATTERN_TYPE_FIXED,
 +      GREP_PATTERN_TYPE_PCRE
 +};
 +
  struct grep_expr {
        enum grep_expr_node node;
        unsigned hit;
@@@ -98,6 -90,7 +98,7 @@@ struct grep_opt 
        int word_regexp;
        int fixed;
        int all_match;
+       int debug;
  #define GREP_BINARY_DEFAULT   0
  #define GREP_BINARY_NOMATCH   1
  #define GREP_BINARY_TEXT      2
        int max_depth;
        int funcname;
        int funcbody;
 +      int extended_regexp_option;
 +      int pattern_type_option;
        char color_context[COLOR_MAXLEN];
        char color_filename[COLOR_MAXLEN];
        char color_function[COLOR_MAXLEN];
@@@ -158,11 -149,10 +159,10 @@@ struct grep_source 
  
  void grep_source_init(struct grep_source *gs, enum grep_source_type type,
                      const char *name, const void *identifier);
- int grep_source_load(struct grep_source *gs);
  void grep_source_clear_data(struct grep_source *gs);
  void grep_source_clear(struct grep_source *gs);
  void grep_source_load_driver(struct grep_source *gs);
- int grep_source_is_binary(struct grep_source *gs);
  
  int grep_source(struct grep_opt *opt, struct grep_source *gs);
  
diff --combined revision.c
index dc3fecf903aa0ce4927b9ebe5b95f99cc9c1018d,90376e8e19878b8cc67c2f18a92cf05615b90bd8..ae12e11fb74dcc51bfddd2acf94601b2bfba7811
@@@ -345,7 -345,6 +345,7 @@@ static int tree_difference = REV_TREE_S
  static void file_add_remove(struct diff_options *options,
                    int addremove, unsigned mode,
                    const unsigned char *sha1,
 +                  int sha1_valid,
                    const char *fullpath, unsigned dirty_submodule)
  {
        int diff = addremove == '+' ? REV_TREE_NEW : REV_TREE_OLD;
@@@ -359,7 -358,6 +359,7 @@@ static void file_change(struct diff_opt
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
 +               int old_sha1_valid, int new_sha1_valid,
                 const char *fullpath,
                 unsigned old_dirty_submodule, unsigned new_dirty_submodule)
  {
@@@ -1002,7 -1000,7 +1002,7 @@@ static int add_parents_only(struct rev_
                flags ^= UNINTERESTING;
                arg++;
        }
 -      if (get_sha1(arg, sha1))
 +      if (get_sha1_committish(arg, sha1))
                return 0;
        while (1) {
                it = get_reference(revs, arg, sha1, 0);
@@@ -1116,16 -1114,16 +1116,16 @@@ static void prepare_show_merge(struct r
        revs->limited = 1;
  }
  
 -int handle_revision_arg(const char *arg_, struct rev_info *revs,
 -                      int flags,
 -                      int cant_be_filename)
 +int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
  {
 -      unsigned mode;
 +      struct object_context oc;
        char *dotdot;
        struct object *object;
        unsigned char sha1[20];
        int local_flags;
        const char *arg = arg_;
 +      int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
 +      unsigned get_sha1_flags = 0;
  
        dotdot = strstr(arg, "..");
        if (dotdot) {
                const char *this = arg;
                int symmetric = *next == '.';
                unsigned int flags_exclude = flags ^ UNINTERESTING;
 +              static const char head_by_default[] = "HEAD";
                unsigned int a_flags;
  
                *dotdot = 0;
                next += symmetric;
  
                if (!*next)
 -                      next = "HEAD";
 +                      next = head_by_default;
                if (dotdot == arg)
 -                      this = "HEAD";
 -              if (!get_sha1(this, from_sha1) &&
 -                  !get_sha1(next, sha1)) {
 +                      this = head_by_default;
 +              if (this == head_by_default && next == head_by_default &&
 +                  !symmetric) {
 +                      /*
 +                       * Just ".."?  That is not a range but the
 +                       * pathspec for the parent directory.
 +                       */
 +                      if (!cant_be_filename) {
 +                              *dotdot = '.';
 +                              return -1;
 +                      }
 +              }
 +              if (!get_sha1_committish(this, from_sha1) &&
 +                  !get_sha1_committish(next, sha1)) {
                        struct commit *a, *b;
                        struct commit_list *exclude;
  
                local_flags = UNINTERESTING;
                arg++;
        }
 -      if (get_sha1_with_mode(arg, sha1, &mode))
 +
 +      if (revarg_opt & REVARG_COMMITTISH)
 +              get_sha1_flags = GET_SHA1_COMMITTISH;
 +
 +      if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
                return revs->ignore_missing ? 0 : -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
        add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
 -      add_pending_object_with_mode(revs, object, arg, mode);
 +      add_pending_object_with_mode(revs, object, arg, oc.mode);
        return 0;
  }
  
@@@ -1275,7 -1257,7 +1275,7 @@@ static void read_revisions_from_stdin(s
                        }
                        die("options not supported in --stdin mode");
                }
 -              if (handle_revision_arg(sb.buf, revs, 0, 1))
 +              if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
        if (seen_dashdash)
@@@ -1312,7 -1294,7 +1312,7 @@@ static int handle_revision_opt(struct r
            !strcmp(arg, "--no-walk") || !strcmp(arg, "--do-walk") ||
            !strcmp(arg, "--bisect") || !prefixcmp(arg, "--glob=") ||
            !prefixcmp(arg, "--branches=") || !prefixcmp(arg, "--tags=") ||
 -          !prefixcmp(arg, "--remotes="))
 +          !prefixcmp(arg, "--remotes=") || !prefixcmp(arg, "--no-walk="))
        {
                unkv[(*unkc)++] = arg;
                return 1;
                revs->topo_order = 1;
        } else if (!strcmp(arg, "--simplify-merges")) {
                revs->simplify_merges = 1;
 +              revs->topo_order = 1;
                revs->rewrite_parents = 1;
                revs->simplify_history = 0;
                revs->limited = 1;
        } else if (!strcmp(arg, "--simplify-by-decoration")) {
                revs->simplify_merges = 1;
 +              revs->topo_order = 1;
                revs->rewrite_parents = 1;
                revs->simplify_history = 0;
                revs->simplify_by_decoration = 1;
        } else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
                add_message_grep(revs, optarg);
                return argcount;
+       } else if (!strcmp(arg, "--grep-debug")) {
+               revs->grep_filter.debug = 1;
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
                revs->grep_filter.regflags |= REG_EXTENDED;
        } else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
@@@ -1707,18 -1689,7 +1709,18 @@@ static int handle_revision_pseudo_opt(c
        } else if (!strcmp(arg, "--not")) {
                *flags ^= UNINTERESTING;
        } else if (!strcmp(arg, "--no-walk")) {
 -              revs->no_walk = 1;
 +              revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
 +      } else if (!prefixcmp(arg, "--no-walk=")) {
 +              /*
 +               * Detached form ("--no-walk X" as opposed to "--no-walk=X")
 +               * not allowed, since the argument is optional.
 +               */
 +              if (!strcmp(arg + 10, "sorted"))
 +                      revs->no_walk = REVISION_WALK_NO_WALK_SORTED;
 +              else if (!strcmp(arg + 10, "unsorted"))
 +                      revs->no_walk = REVISION_WALK_NO_WALK_UNSORTED;
 +              else
 +                      return error("invalid argument to --no-walk");
        } else if (!strcmp(arg, "--do-walk")) {
                revs->no_walk = 0;
        } else {
   */
  int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
  {
 -      int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
 +      int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
        struct cmdline_pathspec prune_data;
        const char *submodule = NULL;
  
  
        /* Second, deal with arguments and options */
        flags = 0;
 +      revarg_opt = opt ? opt->revarg_opt : 0;
 +      if (seen_dashdash)
 +              revarg_opt |= REVARG_CANNOT_BE_FILENAME;
        read_from_stdin = 0;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
                        continue;
                }
  
 -              if (handle_revision_arg(arg, revs, flags, seen_dashdash)) {
 +
 +              if (handle_revision_arg(arg, revs, flags, revarg_opt)) {
                        int j;
                        if (seen_dashdash || *arg == '^')
                                die("bad revision '%s'", arg);
                         * but the latter we have checked in the main loop.
                         */
                        for (j = i; j < argc; j++)
 -                              verify_filename(revs->prefix, argv[j]);
 +                              verify_filename(revs->prefix, argv[j], j == i);
  
                        append_prune_data(&prune_data, argv + i);
                        break;
        if (revs->def && !revs->pending.nr && !got_rev_arg) {
                unsigned char sha1[20];
                struct object *object;
 -              unsigned mode;
 -              if (get_sha1_with_mode(revs->def, sha1, &mode))
 +              struct object_context oc;
 +              if (get_sha1_with_context(revs->def, 0, sha1, &oc))
                        die("bad default revision '%s'", revs->def);
                object = get_reference(revs, revs->def, sha1, 0);
 -              add_pending_object_with_mode(revs, object, revs->def, mode);
 +              add_pending_object_with_mode(revs, object, revs->def, oc.mode);
        }
  
        /* Did the user ask for any diff output? Run the diff! */
        if (revs->combine_merges)
                revs->ignore_merges = 0;
        revs->diffopt.abbrev = revs->abbrev;
 -      if (diff_setup_done(&revs->diffopt) < 0)
 -              die("diff_setup_done failed");
 +      diff_setup_done(&revs->diffopt);
  
        compile_grep_patterns(&revs->grep_filter);
  
@@@ -1981,9 -1949,8 +1983,9 @@@ static struct commit_list **simplify_on
        }
  
        /*
 -       * Do we know what commit all of our parents should be rewritten to?
 -       * Otherwise we are not ready to rewrite this one yet.
 +       * Do we know what commit all of our parents that matter
 +       * should be rewritten to?  Otherwise we are not ready to
 +       * rewrite this one yet.
         */
        for (cnt = 0, p = commit->parents; p; p = p->next) {
                pst = locate_simplify_state(revs, p->item);
                        tail = &commit_list_insert(p->item, tail)->next;
                        cnt++;
                }
 +              if (revs->first_parent_only)
 +                      break;
        }
        if (cnt) {
                tail = &commit_list_insert(commit, tail)->next;
        for (p = commit->parents; p; p = p->next) {
                pst = locate_simplify_state(revs, p->item);
                p->item = pst->simplified;
 +              if (revs->first_parent_only)
 +                      break;
        }
 -      cnt = remove_duplicate_parents(commit);
 +      if (!revs->first_parent_only)
 +              cnt = remove_duplicate_parents(commit);
 +      else
 +              cnt = 1;
  
        /*
         * It is possible that we are a merge and one side branch
  
  static void simplify_merges(struct rev_info *revs)
  {
 -      struct commit_list *list;
 +      struct commit_list *list, *next;
        struct commit_list *yet_to_do, **tail;
 +      struct commit *commit;
  
 -      if (!revs->topo_order)
 -              sort_in_topological_order(&revs->commits, revs->lifo);
        if (!revs->prune)
                return;
  
        /* feed the list reversed */
        yet_to_do = NULL;
 -      for (list = revs->commits; list; list = list->next)
 -              commit_list_insert(list->item, &yet_to_do);
 +      for (list = revs->commits; list; list = next) {
 +              commit = list->item;
 +              next = list->next;
 +              /*
 +               * Do not free(list) here yet; the original list
 +               * is used later in this function.
 +               */
 +              commit_list_insert(commit, &yet_to_do);
 +      }
        while (yet_to_do) {
                list = yet_to_do;
                yet_to_do = NULL;
                tail = &yet_to_do;
                while (list) {
 -                      struct commit *commit = list->item;
 -                      struct commit_list *next = list->next;
 +                      commit = list->item;
 +                      next = list->next;
                        free(list);
                        list = next;
                        tail = simplify_one(revs, commit, tail);
        revs->commits = NULL;
        tail = &revs->commits;
        while (list) {
 -              struct commit *commit = list->item;
 -              struct commit_list *next = list->next;
                struct merge_simplify_state *st;
 +
 +              commit = list->item;
 +              next = list->next;
                free(list);
                list = next;
                st = locate_simplify_state(revs, commit);
@@@ -2115,16 -2068,10 +2117,16 @@@ static void set_children(struct rev_inf
        }
  }
  
 +void reset_revision_walk(void)
 +{
 +      clear_object_flags(SEEN | ADDED | SHOWN);
 +}
 +
  int prepare_revision_walk(struct rev_info *revs)
  {
        int nr = revs->pending.nr;
        struct object_array_entry *e, *list;
 +      struct commit_list **next = &revs->commits;
  
        e = list = revs->pending.objects;
        revs->pending.nr = 0;
                if (commit) {
                        if (!(commit->object.flags & SEEN)) {
                                commit->object.flags |= SEEN;
 -                              commit_list_insert_by_date(commit, &revs->commits);
 +                              next = commit_list_append(commit, next);
                        }
                }
                e++;
        if (!revs->leak_pending)
                free(list);
  
 +      if (revs->no_walk != REVISION_WALK_NO_WALK_UNSORTED)
 +              commit_list_sort_by_date(&revs->commits);
        if (revs->no_walk)
                return 0;
        if (revs->limited)
@@@ -2394,28 -2339,29 +2396,28 @@@ static struct commit *get_revision_inte
        }
  
        /*
 -       * Now pick up what they want to give us
 +       * If our max_count counter has reached zero, then we are done. We
 +       * don't simply return NULL because we still might need to show
 +       * boundary commits. But we want to avoid calling get_revision_1, which
 +       * might do a considerable amount of work finding the next commit only
 +       * for us to throw it away.
 +       *
 +       * If it is non-zero, then either we don't have a max_count at all
 +       * (-1), or it is still counting, in which case we decrement.
         */
 -      c = get_revision_1(revs);
 -      if (c) {
 -              while (0 < revs->skip_count) {
 -                      revs->skip_count--;
 -                      c = get_revision_1(revs);
 -                      if (!c)
 -                              break;
 +      if (revs->max_count) {
 +              c = get_revision_1(revs);
 +              if (c) {
 +                      while (0 < revs->skip_count) {
 +                              revs->skip_count--;
 +                              c = get_revision_1(revs);
 +                              if (!c)
 +                                      break;
 +                      }
                }
 -      }
  
 -      /*
 -       * Check the max_count.
 -       */
 -      switch (revs->max_count) {
 -      case -1:
 -              break;
 -      case 0:
 -              c = NULL;
 -              break;
 -      default:
 -              revs->max_count--;
 +              if (revs->max_count > 0)
 +                      revs->max_count--;
        }
  
        if (c)
diff --combined t/t7810-grep.sh
index 35d357d4c8bdc0ef313a2fc269a9ca81b22489f1,b5c488e3a55304125548afafd8f94ac442930045..91db352cc7ed5faa24eb27542d5ce8c4fe622303
                git -c grep.extendedRegexp=true grep "a+b*c" ab >actual &&
                test_cmp expected actual
        '
 +
 +      test_expect_success "grep $L with grep.patterntype=basic" '
 +              echo "ab:a+bc" >expected &&
 +              git -c grep.patterntype=basic grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success "grep $L with grep.patterntype=extended" '
 +              echo "ab:abc" >expected &&
 +              git -c grep.patterntype=extended grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success "grep $L with grep.patterntype=fixed" '
 +              echo "ab:a+b*c" >expected &&
 +              git -c grep.patterntype=fixed grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success LIBPCRE "grep $L with grep.patterntype=perl" '
 +              echo "ab:a+b*c" >expected &&
 +              git -c grep.patterntype=perl grep "a\x{2b}b\x{2a}c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success "grep $L with grep.patternType=default and grep.extendedRegexp=true" '
 +              echo "ab:abc" >expected &&
 +              git \
 +                      -c grep.patternType=default \
 +                      -c grep.extendedRegexp=true \
 +                      grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success "grep $L with grep.extendedRegexp=true and grep.patternType=default" '
 +              echo "ab:abc" >expected &&
 +              git \
 +                      -c grep.extendedRegexp=true \
 +                      -c grep.patternType=default \
 +                      grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success 'grep $L with grep.patternType=extended and grep.extendedRegexp=false' '
 +              echo "ab:abc" >expected &&
 +              git \
 +                      -c grep.patternType=extended \
 +                      -c grep.extendedRegexp=false \
 +                      grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success 'grep $L with grep.patternType=basic and grep.extendedRegexp=true' '
 +              echo "ab:a+bc" >expected &&
 +              git \
 +                      -c grep.patternType=basic \
 +                      -c grep.extendedRegexp=true \
 +                      grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success 'grep $L with grep.extendedRegexp=false and grep.patternType=extended' '
 +              echo "ab:abc" >expected &&
 +              git \
 +                      -c grep.extendedRegexp=false \
 +                      -c grep.patternType=extended \
 +                      grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
 +
 +      test_expect_success 'grep $L with grep.extendedRegexp=true and grep.patternType=basic' '
 +              echo "ab:a+bc" >expected &&
 +              git \
 +                      -c grep.extendedRegexp=true \
 +                      -c grep.patternType=basic \
 +                      grep "a+b*c" ab >actual &&
 +              test_cmp expected actual
 +      '
  done
  
  cat >expected <<EOF
@@@ -477,6 -399,17 +477,6 @@@ test_expect_success 'grep -q, silently 
        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
@@@ -502,31 -435,41 +502,41 @@@ test_expect_success 'log grep setup' 
  
  test_expect_success 'log grep (1)' '
        git log --author=author --pretty=tformat:%s >actual &&
-       ( echo third ; echo initial ) >expect &&
+       {
+               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 &&
+       {
+               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 &&
+       {
+               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 &&
+       {
+               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 &&
+       {
+               echo third && echo initial
+       } >expect &&
        test_cmp expect actual
  '
  
@@@ -536,11 -479,19 +546,19 @@@ 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_expect_success 'log with multiple --grep uses union' '
+       git log --grep=i --grep=r --format=%s >actual &&
+       {
+               echo fourth && echo third && echo initial
+       } >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'log --all-match with multiple --grep uses intersection' '
+       git log --all-match --grep=i --grep=r --format=%s >actual &&
+       {
+               echo third
+       } >expect &&
        test_cmp expect actual
  '
  
@@@ -552,7 -503,47 +570,47 @@@ test_expect_success 'log with multiple 
        test_cmp expect actual
  '
  
- test_expect_success 'log with --grep and multiple --author uses all-match' '
+ test_expect_success 'log --all-match with multiple --author still uses union' '
+       git log --all-match --author="Thor" --author="Aster" --format=%s >actual &&
+       {
+           echo third && echo second && echo initial
+       } >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'log --grep --author uses intersection' '
+       # grep matches only third and fourth
+       # author matches only initial and third
+       git log --author="A U Thor" --grep=r --format=%s >actual &&
+       {
+               echo third
+       } >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'log --grep --grep --author takes union of greps and intersects with author' '
+       # 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 ---all-match -grep --author --author still takes union of authors and intersects with grep' '
+       # grep matches only initial and third
+       # author matches all but second
+       git log --all-match --author="Thor" --author="Night" --grep=i --format=%s >actual &&
+       {
+           echo third && echo initial
+       } >expect &&
+       test_cmp expect actual
+ '
+ test_expect_success 'log --grep --author --author takes union of authors and intersects with grep' '
+       # grep matches only initial and third
+       # author matches all but second
        git log --author="Thor" --author="Night" --grep=i --format=%s >actual &&
        {
            echo third && echo initial
        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_expect_success 'log --all-match --grep --grep --author takes intersection' '
+       # grep matches only third
+       # author matches only initial and third
+       git log --all-match --author="A U Thor" --grep=i --grep=r --format=%s >actual &&
+       {
+               echo third
+       } >expect &&
        test_cmp expect actual
  '
  
@@@ -839,147 -834,44 +901,147 @@@ test_expect_success 'grep -G invalidpat
        test_must_fail git grep -G "a["
  '
  
 +test_expect_success 'grep invalidpattern properly dies with grep.patternType=basic' '
 +      test_must_fail git -c grep.patterntype=basic grep "a["
 +'
 +
  test_expect_success 'grep -E invalidpattern properly dies ' '
        test_must_fail git grep -E "a["
  '
  
 +test_expect_success 'grep invalidpattern properly dies with grep.patternType=extended' '
 +      test_must_fail git -c grep.patterntype=extended grep "a["
 +'
 +
  test_expect_success LIBPCRE 'grep -P invalidpattern properly dies ' '
        test_must_fail git grep -P "a["
  '
  
 +test_expect_success LIBPCRE 'grep invalidpattern properly dies with grep.patternType=perl' '
 +      test_must_fail git -c grep.patterntype=perl grep "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 pattern with grep.patternType=basic, =extended, =fixed' '
 +      echo "ab:a+b*c" >expected &&
 +      git \
 +              -c grep.patterntype=basic \
 +              -c grep.patterntype=extended \
 +              -c grep.patterntype=fixed \
 +              grep "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 pattern with grep.patternType=extended, =fixed, =basic' '
 +      echo "ab:a+bc" >expected &&
 +      git \
 +              -c grep.patterntype=extended \
 +              -c grep.patterntype=fixed \
 +              -c grep.patterntype=basic \
 +              grep "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 pattern with grep.patternType=fixed, =basic, =extended' '
 +      echo "ab:abc" >expected &&
 +      git \
 +              -c grep.patterntype=fixed \
 +              -c grep.patterntype=basic \
 +              -c grep.patterntype=extended \
 +              grep "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 'grep pattern with grep.patternType=fixed, =basic, =perl, =extended' '
 +      >empty &&
 +      test_must_fail git \
 +              -c grep.patterntype=fixed \
 +              -c grep.patterntype=basic \
 +              -c grep.patterntype=perl \
 +              -c grep.patterntype=extended \
 +              grep "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_expect_success LIBPCRE 'grep pattern with grep.patternType=fixed, =basic, =extended, =perl' '
 +      echo "ab:a+b*c" >expected &&
 +      git \
 +              -c grep.patterntype=fixed \
 +              -c grep.patterntype=basic \
 +              -c grep.patterntype=extended \
 +              -c grep.patterntype=perl \
 +              grep "a\x{2b}b\x{2a}c" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success LIBPCRE 'grep -P pattern with grep.patternType=fixed' '
 +      echo "ab:a+b*c" >expected &&
 +      git \
 +              -c grep.patterntype=fixed \
 +              grep -P "a\x{2b}b\x{2a}c" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep -F pattern with grep.patternType=basic' '
 +      echo "ab:a+b*c" >expected &&
 +      git \
 +              -c grep.patterntype=basic \
 +              grep -F "*c" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep -G pattern with grep.patternType=fixed' '
 +      {
 +              echo "ab:a+b*c"
 +              echo "ab:a+bc"
 +      } >expected &&
 +      git \
 +              -c grep.patterntype=fixed \
 +              grep -G "a+b" ab >actual &&
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'grep -E pattern with grep.patternType=fixed' '
 +      {
 +              echo "ab:a+b*c"
 +              echo "ab:a+bc"
 +              echo "ab:abc"
 +      } >expected &&
 +      git \
 +              -c grep.patterntype=fixed \
 +              grep -E "a+" ab >actual &&
 +      test_cmp expected actual
 +'
 +
  test_config() {
        git config "$1" "$2" &&
        test_when_finished "git config --unset $1"