log --author/--committer: really match only with name part
authorJunio C Hamano <gitster@pobox.com>
Fri, 5 Sep 2008 05:15:02 +0000 (22:15 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 5 Sep 2008 05:21:56 +0000 (22:21 -0700)
When we tried to find commits done by AUTHOR, the first implementation
tried to pattern match a line with "^author .*AUTHOR", which later was
enhanced to strip leading caret and look for "^author AUTHOR" when the
search pattern was anchored at the left end (i.e. --author="^AUTHOR").

This had a few problems:

 * When looking for fixed strings (e.g. "git log -F --author=x --grep=y"),
   the regexp internally used "^author .*x" would never match anything;

 * To match at the end (e.g. "git log --author='google.com>$'"), the
   generated regexp has to also match the trailing timestamp part the
   commit header lines have.  Also, in order to determine if the '$' at
   the end means "match at the end of the line" or just a literal dollar
   sign (probably backslash-quoted), we would need to parse the regexp
   ourselves.

An earlier alternative tried to make sure that a line matches "^author "
(to limit by field name) and the user supplied pattern at the same time.
While it solved the -F problem by introducing a special override for
matching the "^author ", it did not solve the trailing timestamp nor tail
match problem.  It also would have matched every commit if --author=author
was asked for, not because the author's email part had this string, but
because every commit header line that talks about the author begins with
that field name, regardleses of who wrote it.

Instead of piling more hacks on top of hacks, this rethinks the grep
machinery that is used to look for strings in the commit header, and makes
sure that (1) field name matches literally at the beginning of the line,
followed by a SP, and (2) the user supplied pattern is matched against the
remainder of the line, excluding the trailing timestamp data.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
grep.c
grep.h
revision.c
t/t7002-grep.sh

diff --git a/grep.c b/grep.c
index f67d6716ea5f42c3384a7a4cb2eb973b02785fba..706351197fc26efa10c4666d38433f8fbdf1d6b5 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -2,6 +2,19 @@
 #include "grep.h"
 #include "xdiff-interface.h"
 
+void append_header_grep_pattern(struct grep_opt *opt, enum grep_header_field field, const char *pat)
+{
+       struct grep_pat *p = xcalloc(1, sizeof(*p));
+       p->pattern = pat;
+       p->origin = "header";
+       p->no = 0;
+       p->token = GREP_PATTERN_HEAD;
+       p->field = field;
+       *opt->pattern_tail = p;
+       opt->pattern_tail = &p->next;
+       p->next = NULL;
+}
+
 void append_grep_pattern(struct grep_opt *opt, const char *pat,
                         const char *origin, int no, enum grep_pat_token t)
 {
@@ -247,16 +260,53 @@ static int fixmatch(const char *pattern, char *line, regmatch_t *match)
        }
 }
 
+static int strip_timestamp(char *bol, char **eol_p)
+{
+       char *eol = *eol_p;
+       int ch;
+
+       while (bol < --eol) {
+               if (*eol != '>')
+                       continue;
+               *eol_p = ++eol;
+               ch = *eol;
+               *eol = '\0';
+               return ch;
+       }
+       return 0;
+}
+
+static struct {
+       const char *field;
+       size_t len;
+} header_field[] = {
+       { "author ", 7 },
+       { "committer ", 10 },
+};
+
 static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
 {
        int hit = 0;
        int at_true_bol = 1;
+       int saved_ch = 0;
        regmatch_t pmatch[10];
 
        if ((p->token != GREP_PATTERN) &&
            ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
                return 0;
 
+       if (p->token == GREP_PATTERN_HEAD) {
+               const char *field;
+               size_t len;
+               assert(p->field < ARRAY_SIZE(header_field));
+               field = header_field[p->field].field;
+               len = header_field[p->field].len;
+               if (strncmp(bol, field, len))
+                       return 0;
+               bol += len;
+               saved_ch = strip_timestamp(bol, &eol);
+       }
+
  again:
        if (!opt->fixed) {
                regex_t *exp = &p->regexp;
@@ -298,6 +348,8 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
                        goto again;
                }
        }
+       if (p->token == GREP_PATTERN_HEAD && saved_ch)
+               *eol = saved_ch;
        return hit;
 }
 
diff --git a/grep.h b/grep.h
index d252dd25f81526d9b8663b4d3c9585d69a901397..59b3f871ea63619f8d3caae74c41b4da1e9a2b9f 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -17,12 +17,18 @@ enum grep_context {
        GREP_CONTEXT_BODY,
 };
 
+enum grep_header_field {
+       GREP_HEADER_AUTHOR = 0,
+       GREP_HEADER_COMMITTER,
+};
+
 struct grep_pat {
        struct grep_pat *next;
        const char *origin;
        int no;
        enum grep_pat_token token;
        const char *pattern;
+       enum grep_header_field field;
        regex_t regexp;
 };
 
@@ -74,6 +80,7 @@ struct grep_opt {
 };
 
 extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
+extern void append_header_grep_pattern(struct grep_opt *, enum grep_header_field, const char *);
 extern void compile_grep_patterns(struct grep_opt *opt);
 extern void free_grep_patterns(struct grep_opt *opt);
 extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
index 36291b6b864a1213841aba91d58693324d1c88c7..270294af8321c0b81f720db0f36e8f7f9eddbc88 100644 (file)
@@ -953,22 +953,9 @@ static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token
        append_grep_pattern(&revs->grep_filter, ptn, "command line", 0, what);
 }
 
-static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
+static void add_header_grep(struct rev_info *revs, enum grep_header_field field, const char *pattern)
 {
-       char *pat;
-       const char *prefix;
-       int patlen, fldlen;
-
-       fldlen = strlen(field);
-       patlen = strlen(pattern);
-       pat = xmalloc(patlen + fldlen + 10);
-       prefix = ".*";
-       if (*pattern == '^') {
-               prefix = "";
-               pattern++;
-       }
-       sprintf(pat, "^%s %s%s", field, prefix, pattern);
-       add_grep(revs, pat, GREP_PATTERN_HEAD);
+       append_header_grep_pattern(&revs->grep_filter, field, pattern);
 }
 
 static void add_message_grep(struct rev_info *revs, const char *pattern)
@@ -1154,9 +1141,9 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
         * Grepping the commit log
         */
        else if (!prefixcmp(arg, "--author=")) {
-               add_header_grep(revs, "author", arg+9);
+               add_header_grep(revs, GREP_HEADER_AUTHOR, arg+9);
        } else if (!prefixcmp(arg, "--committer=")) {
-               add_header_grep(revs, "committer", arg+12);
+               add_header_grep(revs, GREP_HEADER_COMMITTER, arg+12);
        } else if (!prefixcmp(arg, "--grep=")) {
                add_message_grep(revs, arg+7);
        } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
index c8b4f65f380f3941c75bd6ed52975777d2b28d67..5e359cb561b6d6f7c1bae678b7b823ed2ce34cf1 100755 (executable)
@@ -22,6 +22,7 @@ test_expect_success setup '
        mkdir t &&
        echo test >t/t &&
        git add file x y z t/t &&
+       test_tick &&
        git commit -m initial
 '
 
@@ -113,4 +114,54 @@ do
 
 done
 
+test_expect_success 'log grep setup' '
+       echo a >>file &&
+       test_tick &&
+       GIT_AUTHOR_NAME="With * Asterisk" \
+       GIT_AUTHOR_EMAIL="xyzzy@frotz.com" \
+       git commit -a -m "second" &&
+
+       echo a >>file &&
+       test_tick &&
+       git commit -a -m "third"
+
+'
+
+test_expect_success 'log grep (1)' '
+       git log --author=author --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (2)' '
+       git log --author=" * " -F --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (3)' '
+       git log --author="^A U" --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (4)' '
+       git log --author="frotz\.com>$" --pretty=tformat:%s >actual &&
+       ( echo second ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (5)' '
+       git log --author=Thor -F --grep=Thu --pretty=tformat:%s >actual &&
+       ( echo third ; echo initial ) >expect &&
+       test_cmp expect actual
+'
+
+test_expect_success 'log grep (6)' '
+       git log --author=-0700  --pretty=tformat:%s >actual &&
+       >expect &&
+       test_cmp expect actual
+
+'
+
 test_done