pretty: colorize pattern matches in commit messages

The "git log" command limits its output to the commits that contain strings matched by a pattern when the "--grep=<pattern>" option is used, but unlike output from "git grep -e <pattern>", the matches are not highlighted, making them harder to spot. Teach the pretty-printer code to highlight matches from the "--grep=<pattern>", "--author=<pattern>" and "--committer=<pattern>" options (to view the last one, you may have to ask for --pretty=fuller). Also, it must be noted that we are effectively greping the content twice (because it would be a hassle to rework the existing matching code to do a /g match and then pass it all down to the coloring code), however it only slows down "git log --author=^H" on this repository by around 1-2% (compared to v2.33.0), so it should be a small enough slow down to justify the addition of the feature. Signed-off-by: Hamza Mahfooz <someguy@effective-light.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Hamza Mahfooz committed Oct 7, 2021 at 16:31 UTC 6a5c337922a5221d1f6d025d84e18b526df9944c
3 files changed +145 -14
Documentation/config/color.txt
+5 -2
@@ -104,9 +104,12 @@ color.grep.<slot>::
104 `matchContext`;;
105 matching text in context lines
106 `matchSelected`;;
107 - matching text in selected lines
107 + matching text in selected lines. Also, used to customize the following
108 + linkgit:git-log[1] subcommands: `--grep`, `--author` and `--committer`.
109 `selected`;;
109 - non-matching text in selected lines
110 + non-matching text in selected lines. Also, used to customize the
111 + following linkgit:git-log[1] subcommands: `--grep`, `--author` and
112 + `--committer`.
113 `separator`;;
114 separators between fields on a line (`:`, `-`, and `=`)
115 and between hunks (`--`)
pretty.c
+89 -12
@@ -431,6 +431,52 @@ const char *show_ident_date(const struct ident_split *ident,
431 return show_date(date, tz, mode);
432 }
433
434 +static inline void strbuf_add_with_color(struct strbuf *sb, const char *color,
435 + const char *buf, size_t buflen)
436 +{
437 + strbuf_addstr(sb, color);
438 + strbuf_add(sb, buf, buflen);
439 + if (*color)
440 + strbuf_addstr(sb, GIT_COLOR_RESET);
441 +}
442 +
443 +static void append_line_with_color(struct strbuf *sb, struct grep_opt *opt,
444 + const char *line, size_t linelen,
445 + int color, enum grep_context ctx,
446 + enum grep_header_field field)
447 +{
448 + const char *buf, *eol, *line_color, *match_color;
449 + regmatch_t match;
450 + int eflags = 0;
451 +
452 + buf = line;
453 + eol = buf + linelen;
454 +
455 + if (!opt || !want_color(color) || opt->invert)
456 + goto end;
457 +
458 + line_color = opt->colors[GREP_COLOR_SELECTED];
459 + match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];
460 +
461 + while (grep_next_match(opt, buf, eol, ctx, &match, field, eflags)) {
462 + if (match.rm_so == match.rm_eo)
463 + break;
464 +
465 + strbuf_add_with_color(sb, line_color, buf, match.rm_so);
466 + strbuf_add_with_color(sb, match_color, buf + match.rm_so,
467 + match.rm_eo - match.rm_so);
468 + buf += match.rm_eo;
469 + eflags = REG_NOTBOL;
470 + }
471 +
472 + if (eflags)
473 + strbuf_add_with_color(sb, line_color, buf, eol - buf);
474 + else {
475 +end:
476 + strbuf_add(sb, buf, eol - buf);
477 + }
478 +}
479 +
480 void pp_user_info(struct pretty_print_context *pp,
481 const char *what, struct strbuf *sb,
482 const char *line, const char *encoding)
@@ -496,9 +542,26 @@ void pp_user_info(struct pretty_print_context *pp,
542 strbuf_addch(sb, '\n');
543 strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);
544 } else {
499 - strbuf_addf(sb, "%s: %.*s%.*s <%.*s>\n", what,
500 - (pp->fmt == CMIT_FMT_FULLER) ? 4 : 0, " ",
501 - (int)namelen, namebuf, (int)maillen, mailbuf);
545 + struct strbuf id = STRBUF_INIT;
546 + enum grep_header_field field = GREP_HEADER_FIELD_MAX;
547 + struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
548 +
549 + if (!strcmp(what, "Author"))
550 + field = GREP_HEADER_AUTHOR;
551 + else if (!strcmp(what, "Commit"))
552 + field = GREP_HEADER_COMMITTER;
553 +
554 + strbuf_addf(sb, "%s: ", what);
555 + if (pp->fmt == CMIT_FMT_FULLER)
556 + strbuf_addchars(sb, ' ', 4);
557 +
558 + strbuf_addf(&id, "%.*s <%.*s>", (int)namelen, namebuf,
559 + (int)maillen, mailbuf);
560 +
561 + append_line_with_color(sb, opt, id.buf, id.len, pp->color,
562 + GREP_CONTEXT_HEAD, field);
563 + strbuf_addch(sb, '\n');
564 + strbuf_release(&id);
565 }
566
567 switch (pp->fmt) {
@@ -1939,8 +2002,9 @@ static int pp_utf8_width(const char *start, const char *end)
2002 return width;
2003 }
2004
1942 -static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth,
1943 - const char *line, int linelen)
2005 +static void strbuf_add_tabexpand(struct strbuf *sb, struct grep_opt *opt,
2006 + int color, int tabwidth, const char *line,
2007 + int linelen)
2008 {
2009 const char *tab;
2010
@@ -1957,7 +2021,9 @@ static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth,
2021 break;
2022
2023 /* Output the data .. */
1960 - strbuf_add(sb, line, tab - line);
2024 + append_line_with_color(sb, opt, line, tab - line, color,
2025 + GREP_CONTEXT_BODY,
2026 + GREP_HEADER_FIELD_MAX);
2027
2028 /* .. and the de-tabified tab */
2029 strbuf_addchars(sb, ' ', tabwidth - (width % tabwidth));
@@ -1972,7 +2038,8 @@ static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth,
2038 * worrying about width - there's nothing more to
2039 * align.
2040 */
1975 - strbuf_add(sb, line, linelen);
2041 + append_line_with_color(sb, opt, line, linelen, color, GREP_CONTEXT_BODY,
2042 + GREP_HEADER_FIELD_MAX);
2043 }
2044
2045 /*
@@ -1984,11 +2051,16 @@ static void pp_handle_indent(struct pretty_print_context *pp,
2051 struct strbuf *sb, int indent,
2052 const char *line, int linelen)
2053 {
2054 + struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
2055 +
2056 strbuf_addchars(sb, ' ', indent);
2057 if (pp->expand_tabs_in_log)
1989 - strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, line, linelen);
2058 + strbuf_add_tabexpand(sb, opt, pp->color, pp->expand_tabs_in_log,
2059 + line, linelen);
2060 else
1991 - strbuf_add(sb, line, linelen);
2061 + append_line_with_color(sb, opt, line, linelen, pp->color,
2062 + GREP_CONTEXT_BODY,
2063 + GREP_HEADER_FIELD_MAX);
2064 }
2065
2066 static int is_mboxrd_from(const char *line, int len)
@@ -2006,7 +2078,9 @@ void pp_remainder(struct pretty_print_context *pp,
2078 struct strbuf *sb,
2079 int indent)
2080 {
2081 + struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
2082 int first = 1;
2083 +
2084 for (;;) {
2085 const char *line = *msg_p;
2086 int linelen = get_one_line(line);
@@ -2027,14 +2101,17 @@ void pp_remainder(struct pretty_print_context *pp,
2101 if (indent)
2102 pp_handle_indent(pp, sb, indent, line, linelen);
2103 else if (pp->expand_tabs_in_log)
2030 - strbuf_add_tabexpand(sb, pp->expand_tabs_in_log,
2031 - line, linelen);
2104 + strbuf_add_tabexpand(sb, opt, pp->color,
2105 + pp->expand_tabs_in_log, line,
2106 + linelen);
2107 else {
2108 if (pp->fmt == CMIT_FMT_MBOXRD &&
2109 is_mboxrd_from(line, linelen))
2110 strbuf_addch(sb, '>');
2111
2037 - strbuf_add(sb, line, linelen);
2112 + append_line_with_color(sb, opt, line, linelen,
2113 + pp->color, GREP_CONTEXT_BODY,
2114 + GREP_HEADER_FIELD_MAX);
2115 }
2116 strbuf_addch(sb, '\n');
2117 }
t/t4202-log.sh
+51
@@ -449,6 +449,57 @@ test_expect_success !FAIL_PREREQS 'log with various grep.patternType configurati
449 )
450 '
451
452 +test_expect_success 'log --author' '
453 + cat >expect <<-\EOF &&
454 + Author: <BOLD;RED>A U<RESET> Thor <author@example.com>
455 + EOF
456 + git log -1 --color=always --author="A U" >log &&
457 + grep Author log >actual.raw &&
458 + test_decode_color <actual.raw >actual &&
459 + test_cmp expect actual
460 +'
461 +
462 +test_expect_success 'log --committer' '
463 + cat >expect <<-\EOF &&
464 + Commit: C O Mitter <committer@<BOLD;RED>example<RESET>.com>
465 + EOF
466 + git log -1 --color=always --pretty=fuller --committer="example" >log &&
467 + grep "Commit:" log >actual.raw &&
468 + test_decode_color <actual.raw >actual &&
469 + test_cmp expect actual
470 +'
471 +
472 +test_expect_success 'log -i --grep with color' '
473 + cat >expect <<-\EOF &&
474 + <BOLD;RED>Sec<RESET>ond
475 + <BOLD;RED>sec<RESET>ond
476 + EOF
477 + git log --color=always -i --grep=^sec >log &&
478 + grep -i sec log >actual.raw &&
479 + test_decode_color <actual.raw >actual &&
480 + test_cmp expect actual
481 +'
482 +
483 +test_expect_success '-c color.grep.selected log --grep' '
484 + cat >expect <<-\EOF &&
485 + <GREEN>th<RESET><BOLD;RED>ir<RESET><GREEN>d<RESET>
486 + EOF
487 + git -c color.grep.selected="green" log --color=always --grep=ir >log &&
488 + grep ir log >actual.raw &&
489 + test_decode_color <actual.raw >actual &&
490 + test_cmp expect actual
491 +'
492 +
493 +test_expect_success '-c color.grep.matchSelected log --grep' '
494 + cat >expect <<-\EOF &&
495 + <BLUE>i<RESET>n<BLUE>i<RESET>t<BLUE>i<RESET>al
496 + EOF
497 + git -c color.grep.matchSelected="blue" log --color=always --grep=i >log &&
498 + grep al log >actual.raw &&
499 + test_decode_color <actual.raw >actual &&
500 + test_cmp expect actual
501 +'
502 +
503 cat > expect <<EOF
504 * Second
505 * sixth