Skip to content

Commit b93d720

Browse files
committed
Merge branch 'hm/paint-hits-in-log-grep'
"git log --grep=string --author=name" learns to highlight hits just like "git grep string" does. * hm/paint-hits-in-log-grep: grep/pcre2: fix an edge case concerning ascii patterns and UTF-8 data pretty: colorize pattern matches in commit messages grep: refactor next_match() and match_one_pattern() for external use
2 parents 7e27bd5 + ae39ba4 commit b93d720

File tree

6 files changed

+255
-46
lines changed

6 files changed

+255
-46
lines changed

Documentation/config/color.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ color.grep.<slot>::
105105
`matchContext`;;
106106
matching text in context lines
107107
`matchSelected`;;
108-
matching text in selected lines
108+
matching text in selected lines. Also, used to customize the following
109+
linkgit:git-log[1] subcommands: `--grep`, `--author` and `--committer`.
109110
`selected`;;
110-
non-matching text in selected lines
111+
non-matching text in selected lines. Also, used to customize the
112+
following linkgit:git-log[1] subcommands: `--grep`, `--author` and
113+
`--committer`.
111114
`separator`;;
112115
separators between fields on a line (`:`, `-`, and `=`)
113116
and between hunks (`--`)

grep.c

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,10 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt
382382
}
383383
options |= PCRE2_CASELESS;
384384
}
385-
if (!opt->ignore_locale && is_utf8_locale() && has_non_ascii(p->pattern) &&
386-
!(!opt->ignore_case && (p->fixed || p->is_fixed)))
385+
if ((!opt->ignore_locale && !has_non_ascii(p->pattern)) ||
386+
(!opt->ignore_locale && is_utf8_locale() &&
387+
has_non_ascii(p->pattern) && !(!opt->ignore_case &&
388+
(p->fixed || p->is_fixed))))
387389
options |= (PCRE2_UTF | PCRE2_MATCH_INVALID_UTF);
388390

389391
#ifdef GIT_PCRE2_VERSION_10_36_OR_HIGHER
@@ -944,10 +946,10 @@ static struct {
944946
{ "reflog ", 7 },
945947
};
946948

947-
static int match_one_pattern(struct grep_pat *p,
948-
const char *bol, const char *eol,
949-
enum grep_context ctx,
950-
regmatch_t *pmatch, int eflags)
949+
static int headerless_match_one_pattern(struct grep_pat *p,
950+
const char *bol, const char *eol,
951+
enum grep_context ctx,
952+
regmatch_t *pmatch, int eflags)
951953
{
952954
int hit = 0;
953955
const char *start = bol;
@@ -956,25 +958,6 @@ static int match_one_pattern(struct grep_pat *p,
956958
((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
957959
return 0;
958960

959-
if (p->token == GREP_PATTERN_HEAD) {
960-
const char *field;
961-
size_t len;
962-
assert(p->field < ARRAY_SIZE(header_field));
963-
field = header_field[p->field].field;
964-
len = header_field[p->field].len;
965-
if (strncmp(bol, field, len))
966-
return 0;
967-
bol += len;
968-
switch (p->field) {
969-
case GREP_HEADER_AUTHOR:
970-
case GREP_HEADER_COMMITTER:
971-
strip_timestamp(bol, &eol);
972-
break;
973-
default:
974-
break;
975-
}
976-
}
977-
978961
again:
979962
hit = patmatch(p, bol, eol, pmatch, eflags);
980963

@@ -1025,6 +1008,36 @@ static int match_one_pattern(struct grep_pat *p,
10251008
return hit;
10261009
}
10271010

1011+
static int match_one_pattern(struct grep_pat *p,
1012+
const char *bol, const char *eol,
1013+
enum grep_context ctx, regmatch_t *pmatch,
1014+
int eflags)
1015+
{
1016+
const char *field;
1017+
size_t len;
1018+
1019+
if (p->token == GREP_PATTERN_HEAD) {
1020+
assert(p->field < ARRAY_SIZE(header_field));
1021+
field = header_field[p->field].field;
1022+
len = header_field[p->field].len;
1023+
if (strncmp(bol, field, len))
1024+
return 0;
1025+
bol += len;
1026+
1027+
switch (p->field) {
1028+
case GREP_HEADER_AUTHOR:
1029+
case GREP_HEADER_COMMITTER:
1030+
strip_timestamp(bol, &eol);
1031+
break;
1032+
default:
1033+
break;
1034+
}
1035+
}
1036+
1037+
return headerless_match_one_pattern(p, bol, eol, ctx, pmatch, eflags);
1038+
}
1039+
1040+
10281041
static int match_expr_eval(struct grep_opt *opt, struct grep_expr *x,
10291042
const char *bol, const char *eol,
10301043
enum grep_context ctx, ssize_t *col,
@@ -1143,7 +1156,7 @@ static int match_next_pattern(struct grep_pat *p,
11431156
{
11441157
regmatch_t match;
11451158

1146-
if (!match_one_pattern(p, bol, eol, ctx, &match, eflags))
1159+
if (!headerless_match_one_pattern(p, bol, eol, ctx, &match, eflags))
11471160
return 0;
11481161
if (match.rm_so < 0 || match.rm_eo < 0)
11491162
return 0;
@@ -1158,19 +1171,26 @@ static int match_next_pattern(struct grep_pat *p,
11581171
return 1;
11591172
}
11601173

1161-
static int next_match(struct grep_opt *opt,
1162-
const char *bol, const char *eol,
1163-
enum grep_context ctx, regmatch_t *pmatch, int eflags)
1174+
int grep_next_match(struct grep_opt *opt,
1175+
const char *bol, const char *eol,
1176+
enum grep_context ctx, regmatch_t *pmatch,
1177+
enum grep_header_field field, int eflags)
11641178
{
11651179
struct grep_pat *p;
11661180
int hit = 0;
11671181

11681182
pmatch->rm_so = pmatch->rm_eo = -1;
11691183
if (bol < eol) {
1170-
for (p = opt->pattern_list; p; p = p->next) {
1184+
for (p = ((ctx == GREP_CONTEXT_HEAD)
1185+
? opt->header_list : opt->pattern_list);
1186+
p; p = p->next) {
11711187
switch (p->token) {
1172-
case GREP_PATTERN: /* atom */
11731188
case GREP_PATTERN_HEAD:
1189+
if ((field != GREP_HEADER_FIELD_MAX) &&
1190+
(p->field != field))
1191+
continue;
1192+
/* fall thru */
1193+
case GREP_PATTERN: /* atom */
11741194
case GREP_PATTERN_BODY:
11751195
hit |= match_next_pattern(p, bol, eol, ctx,
11761196
pmatch, eflags);
@@ -1261,7 +1281,8 @@ static void show_line(struct grep_opt *opt,
12611281
else if (sign == '=')
12621282
line_color = opt->colors[GREP_COLOR_FUNCTION];
12631283
}
1264-
while (next_match(opt, bol, eol, ctx, &match, eflags)) {
1284+
while (grep_next_match(opt, bol, eol, ctx, &match,
1285+
GREP_HEADER_FIELD_MAX, eflags)) {
12651286
if (match.rm_so == match.rm_eo)
12661287
break;
12671288

grep.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,15 @@ void compile_grep_patterns(struct grep_opt *opt);
191191
void free_grep_patterns(struct grep_opt *opt);
192192
int grep_buffer(struct grep_opt *opt, const char *buf, unsigned long size);
193193

194+
/* The field parameter is only used to filter header patterns
195+
* (where appropriate). If filtering isn't desirable
196+
* GREP_HEADER_FIELD_MAX should be supplied.
197+
*/
198+
int grep_next_match(struct grep_opt *opt,
199+
const char *bol, const char *eol,
200+
enum grep_context ctx, regmatch_t *pmatch,
201+
enum grep_header_field field, int eflags);
202+
194203
struct grep_source {
195204
char *name;
196205

pretty.c

Lines changed: 89 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,52 @@ const char *show_ident_date(const struct ident_split *ident,
431431
return show_date(date, tz, mode);
432432
}
433433

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+
434480
void pp_user_info(struct pretty_print_context *pp,
435481
const char *what, struct strbuf *sb,
436482
const char *line, const char *encoding)
@@ -496,9 +542,26 @@ void pp_user_info(struct pretty_print_context *pp,
496542
strbuf_addch(sb, '\n');
497543
strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);
498544
} 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);
502565
}
503566

504567
switch (pp->fmt) {
@@ -1935,8 +1998,9 @@ static int pp_utf8_width(const char *start, const char *end)
19351998
return width;
19361999
}
19372000

1938-
static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth,
1939-
const char *line, int linelen)
2001+
static void strbuf_add_tabexpand(struct strbuf *sb, struct grep_opt *opt,
2002+
int color, int tabwidth, const char *line,
2003+
int linelen)
19402004
{
19412005
const char *tab;
19422006

@@ -1953,7 +2017,9 @@ static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth,
19532017
break;
19542018

19552019
/* Output the data .. */
1956-
strbuf_add(sb, line, tab - line);
2020+
append_line_with_color(sb, opt, line, tab - line, color,
2021+
GREP_CONTEXT_BODY,
2022+
GREP_HEADER_FIELD_MAX);
19572023

19582024
/* .. and the de-tabified tab */
19592025
strbuf_addchars(sb, ' ', tabwidth - (width % tabwidth));
@@ -1968,7 +2034,8 @@ static void strbuf_add_tabexpand(struct strbuf *sb, int tabwidth,
19682034
* worrying about width - there's nothing more to
19692035
* align.
19702036
*/
1971-
strbuf_add(sb, line, linelen);
2037+
append_line_with_color(sb, opt, line, linelen, color, GREP_CONTEXT_BODY,
2038+
GREP_HEADER_FIELD_MAX);
19722039
}
19732040

19742041
/*
@@ -1980,11 +2047,16 @@ static void pp_handle_indent(struct pretty_print_context *pp,
19802047
struct strbuf *sb, int indent,
19812048
const char *line, int linelen)
19822049
{
2050+
struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
2051+
19832052
strbuf_addchars(sb, ' ', indent);
19842053
if (pp->expand_tabs_in_log)
1985-
strbuf_add_tabexpand(sb, pp->expand_tabs_in_log, line, linelen);
2054+
strbuf_add_tabexpand(sb, opt, pp->color, pp->expand_tabs_in_log,
2055+
line, linelen);
19862056
else
1987-
strbuf_add(sb, line, linelen);
2057+
append_line_with_color(sb, opt, line, linelen, pp->color,
2058+
GREP_CONTEXT_BODY,
2059+
GREP_HEADER_FIELD_MAX);
19882060
}
19892061

19902062
static int is_mboxrd_from(const char *line, int len)
@@ -2002,7 +2074,9 @@ void pp_remainder(struct pretty_print_context *pp,
20022074
struct strbuf *sb,
20032075
int indent)
20042076
{
2077+
struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
20052078
int first = 1;
2079+
20062080
for (;;) {
20072081
const char *line = *msg_p;
20082082
int linelen = get_one_line(line);
@@ -2023,14 +2097,17 @@ void pp_remainder(struct pretty_print_context *pp,
20232097
if (indent)
20242098
pp_handle_indent(pp, sb, indent, line, linelen);
20252099
else if (pp->expand_tabs_in_log)
2026-
strbuf_add_tabexpand(sb, pp->expand_tabs_in_log,
2027-
line, linelen);
2100+
strbuf_add_tabexpand(sb, opt, pp->color,
2101+
pp->expand_tabs_in_log, line,
2102+
linelen);
20282103
else {
20292104
if (pp->fmt == CMIT_FMT_MBOXRD &&
20302105
is_mboxrd_from(line, linelen))
20312106
strbuf_addch(sb, '>');
20322107

2033-
strbuf_add(sb, line, linelen);
2108+
append_line_with_color(sb, opt, line, linelen,
2109+
pp->color, GREP_CONTEXT_BODY,
2110+
GREP_HEADER_FIELD_MAX);
20342111
}
20352112
strbuf_addch(sb, '\n');
20362113
}

t/t4202-log.sh

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,57 @@ test_expect_success !FAIL_PREREQS 'log with various grep.patternType configurati
449449
)
450450
'
451451

452+
test_expect_success 'log --author' '
453+
cat >expect <<-\EOF &&
454+
Author: <BOLD;RED>A U<RESET> Thor <[email protected]>
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+
452503
cat > expect <<EOF
453504
* Second
454505
* sixth

0 commit comments

Comments
 (0)