Skip to content

Commit 9d8db06

Browse files
ttaylorrgitster
authored andcommitted
grep.c: teach 'git grep --only-matching'
Teach 'git grep --only-matching', a new option to only print the matching part(s) of a line. For instance, a line containing the following (taken from README.md:27): (`man gitcvs-migration` or `git help cvs-migration` if git is Is printed as follows: $ git grep --line-number --column --only-matching -e git -- \ README.md | grep ":27" README.md:27:7:git README.md:27:16:git README.md:27:38:git The patch works mostly as one would expect, with the exception of a few considerations that are worth mentioning here. Like GNU grep, this patch ignores --only-matching when --invert (-v) is given. There is a sensible answer here, but parity with the behavior of other tools is preferred. Because a line might contain more than one match, there are special considerations pertaining to when to print line headers, newlines, and how to increment the match column offset. The line header and newlines are handled as a special case within the main loop to avoid polluting the surrounding code with conditionals that have large blocks. Signed-off-by: Taylor Blau <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent c707ded commit 9d8db06

File tree

5 files changed

+63
-17
lines changed

5 files changed

+63
-17
lines changed

Documentation/git-grep.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ SYNOPSIS
1717
[-l | --files-with-matches] [-L | --files-without-match]
1818
[(-O | --open-files-in-pager) [<pager>]]
1919
[-z | --null]
20-
[-c | --count] [--all-match] [-q | --quiet]
20+
[ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]
2121
[--max-depth <depth>]
2222
[--color[=<when>] | --no-color]
2323
[--break] [--heading] [-p | --show-function]
@@ -201,6 +201,11 @@ providing this option will cause it to die.
201201
Output \0 instead of the character that normally follows a
202202
file name.
203203

204+
-o::
205+
--only-matching::
206+
Print only the matched (non-empty) parts of a matching line, with each such
207+
part on a separate output line.
208+
204209
-c::
205210
--count::
206211
Instead of showing every matched line, show the number of

builtin/grep.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
843843
OPT_BOOL_F('z', "null", &opt.null_following_name,
844844
N_("print NUL after filenames"),
845845
PARSE_OPT_NOCOMPLETE),
846+
OPT_BOOL('o', "only-matching", &opt.only_matching,
847+
N_("show only matching parts of a line")),
846848
OPT_BOOL('c', "count", &opt.count,
847849
N_("show the number of matches instead of matching lines")),
848850
OPT__COLOR(&opt.color, N_("highlight matches")),
@@ -962,6 +964,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
962964
if (!opt.pattern_list)
963965
die(_("no pattern given."));
964966

967+
/* --only-matching has no effect with --invert. */
968+
if (opt.invert)
969+
opt.only_matching = 0;
970+
965971
/*
966972
* We have to find "--" in a separate pass, because its presence
967973
* influences how we will parse arguments that come before it.

grep.c

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ void init_grep_defaults(void)
5151
color_set(opt->color_match_selected, GIT_COLOR_BOLD_RED);
5252
color_set(opt->color_selected, "");
5353
color_set(opt->color_sep, GIT_COLOR_CYAN);
54+
opt->only_matching = 0;
5455
opt->color = -1;
5556
opt->output = std_output;
5657
}
@@ -158,6 +159,7 @@ void grep_init(struct grep_opt *opt, const char *prefix)
158159
opt->pattern_tail = &opt->pattern_list;
159160
opt->header_tail = &opt->header_list;
160161

162+
opt->only_matching = def->only_matching;
161163
opt->color = def->color;
162164
opt->extended_regexp_option = def->extended_regexp_option;
163165
opt->pattern_type_option = def->pattern_type_option;
@@ -1446,7 +1448,8 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
14461448
const char *name, unsigned lno, ssize_t cno, char sign)
14471449
{
14481450
int rest = eol - bol;
1449-
const char *match_color, *line_color = NULL;
1451+
const char *match_color = NULL;
1452+
const char *line_color = NULL;
14501453

14511454
if (opt->file_break && opt->last_shown == 0) {
14521455
if (opt->show_hunk_mark)
@@ -1462,39 +1465,55 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
14621465
opt->output(opt, "\n", 1);
14631466
}
14641467
}
1465-
show_line_header(opt, name, lno, cno, sign);
1466-
if (opt->color) {
1468+
if (!opt->only_matching) {
1469+
/*
1470+
* In case the line we're being called with contains more than
1471+
* one match, leave printing each header to the loop below.
1472+
*/
1473+
show_line_header(opt, name, lno, cno, sign);
1474+
}
1475+
if (opt->color || opt->only_matching) {
14671476
regmatch_t match;
14681477
enum grep_context ctx = GREP_CONTEXT_BODY;
14691478
int ch = *eol;
14701479
int eflags = 0;
14711480

1472-
if (sign == ':')
1473-
match_color = opt->color_match_selected;
1474-
else
1475-
match_color = opt->color_match_context;
1476-
if (sign == ':')
1477-
line_color = opt->color_selected;
1478-
else if (sign == '-')
1479-
line_color = opt->color_context;
1480-
else if (sign == '=')
1481-
line_color = opt->color_function;
1481+
if (opt->color) {
1482+
if (sign == ':')
1483+
match_color = opt->color_match_selected;
1484+
else
1485+
match_color = opt->color_match_context;
1486+
if (sign == ':')
1487+
line_color = opt->color_selected;
1488+
else if (sign == '-')
1489+
line_color = opt->color_context;
1490+
else if (sign == '=')
1491+
line_color = opt->color_function;
1492+
}
14821493
*eol = '\0';
14831494
while (next_match(opt, bol, eol, ctx, &match, eflags)) {
14841495
if (match.rm_so == match.rm_eo)
14851496
break;
14861497

1487-
output_color(opt, bol, match.rm_so, line_color);
1498+
if (opt->only_matching)
1499+
show_line_header(opt, name, lno, cno, sign);
1500+
else
1501+
output_color(opt, bol, match.rm_so, line_color);
14881502
output_color(opt, bol + match.rm_so,
14891503
match.rm_eo - match.rm_so, match_color);
1504+
if (opt->only_matching)
1505+
opt->output(opt, "\n", 1);
14901506
bol += match.rm_eo;
1507+
cno += match.rm_eo;
14911508
rest -= match.rm_eo;
14921509
eflags = REG_NOTBOL;
14931510
}
14941511
*eol = ch;
14951512
}
1496-
output_color(opt, bol, rest, line_color);
1497-
opt->output(opt, "\n", 1);
1513+
if (!opt->only_matching) {
1514+
output_color(opt, bol, rest, line_color);
1515+
opt->output(opt, "\n", 1);
1516+
}
14981517
}
14991518

15001519
#ifndef NO_PTHREADS

grep.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ struct grep_opt {
150150
int relative;
151151
int pathname;
152152
int null_following_name;
153+
int only_matching;
153154
int color;
154155
int max_depth;
155156
int funcname;

t/t7810-grep.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,21 @@ do
262262
fi
263263
'
264264

265+
test_expect_success "grep $L (with --column, --only-matching)" '
266+
{
267+
echo ${HC}file:1:5:mmap
268+
echo ${HC}file:2:5:mmap
269+
echo ${HC}file:3:5:mmap
270+
echo ${HC}file:3:13:mmap
271+
echo ${HC}file:4:5:mmap
272+
echo ${HC}file:4:13:mmap
273+
echo ${HC}file:5:5:mmap
274+
echo ${HC}file:5:13:mmap
275+
} >expected &&
276+
git grep --column -n -o -e mmap $H >actual &&
277+
test_cmp expected actual
278+
'
279+
265280
test_expect_success "grep $L (t-1)" '
266281
echo "${HC}t/t:1:test" >expected &&
267282
git grep -n -e test $H >actual &&

0 commit comments

Comments
 (0)