Merge branch 'jc/maint-log-grep-all-match-1' into maint
authorJunio C Hamano <gitster@pobox.com>
Sun, 30 Sep 2012 05:30:56 +0000 (22:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 30 Sep 2012 05:30:56 +0000 (22:30 -0700)
* jc/maint-log-grep-all-match-1:
grep.c: make two symbols really file-scope static this time
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
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 29adb0ac9399002b07942711863fa3b353926468,a7e8df0d409fd431b71d1458cde6252ae119bf18..0654e0b0f693f9cdd4ba2d7f84c110f7ef8b11b4
@@@ -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);
  
@@@ -772,6 -773,9 +773,9 @@@ int cmd_grep(int argc, const char **arg
                           "indicate hit with exit status without output"),
                OPT_BOOLEAN(0, "all-match", &opt.all_match,
                        "show only matches from files that match all patterns"),
+               { OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
+                 "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",
        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 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 523d04123d02cabb8703f91e9bec194c7d737f00,b5c488e3a55304125548afafd8f94ac442930045..3021cf251c96833a815c5de707d794ba2e07774b
@@@ -399,6 -399,17 +399,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
@@@ -424,31 -435,41 +424,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
  '
  
@@@ -458,11 -479,19 +468,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
  '
  
@@@ -474,7 -503,47 +492,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
  '