Skip to content

Commit a017f27

Browse files
committed
Merge branch 'jc/grep-c' into maint
* jc/grep-c: Split grep arguments in a way that does not requires to add /dev/null.
2 parents 9269df9 + d99ebf0 commit a017f27

File tree

2 files changed

+80
-14
lines changed

2 files changed

+80
-14
lines changed

builtin-grep.c

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,78 @@ static int exec_grep(int argc, const char **argv)
187187
else die("maximum number of args exceeded"); \
188188
} while (0)
189189

190+
/*
191+
* If you send a singleton filename to grep, it does not give
192+
* the name of the file. GNU grep has "-H" but we would want
193+
* that behaviour in a portable way.
194+
*
195+
* So we keep two pathnames in argv buffer unsent to grep in
196+
* the main loop if we need to do more than one grep.
197+
*/
198+
static int flush_grep(struct grep_opt *opt,
199+
int argc, int arg0, const char **argv, int *kept)
200+
{
201+
int status;
202+
int count = argc - arg0;
203+
const char *kept_0 = NULL;
204+
205+
if (count <= 2) {
206+
/*
207+
* Because we keep at least 2 paths in the call from
208+
* the main loop (i.e. kept != NULL), and MAXARGS is
209+
* far greater than 2, this usually is a call to
210+
* conclude the grep. However, the user could attempt
211+
* to overflow the argv buffer by giving too many
212+
* options to leave very small number of real
213+
* arguments even for the call in the main loop.
214+
*/
215+
if (kept)
216+
die("insanely many options to grep");
217+
218+
/*
219+
* If we have two or more paths, we do not have to do
220+
* anything special, but we need to push /dev/null to
221+
* get "-H" behaviour of GNU grep portably but when we
222+
* are not doing "-l" nor "-L" nor "-c".
223+
*/
224+
if (count == 1 &&
225+
!opt->name_only &&
226+
!opt->unmatch_name_only &&
227+
!opt->count) {
228+
argv[argc++] = "/dev/null";
229+
argv[argc] = NULL;
230+
}
231+
}
232+
233+
else if (kept) {
234+
/*
235+
* Called because we found many paths and haven't finished
236+
* iterating over the cache yet. We keep two paths
237+
* for the concluding call. argv[argc-2] and argv[argc-1]
238+
* has the last two paths, so save the first one away,
239+
* replace it with NULL while sending the list to grep,
240+
* and recover them after we are done.
241+
*/
242+
*kept = 2;
243+
kept_0 = argv[argc-2];
244+
argv[argc-2] = NULL;
245+
argc -= 2;
246+
}
247+
248+
status = exec_grep(argc, argv);
249+
250+
if (kept_0) {
251+
/*
252+
* Then recover them. Now the last arg is beyond the
253+
* terminating NULL which is at argc, and the second
254+
* from the last is what we saved away in kept_0
255+
*/
256+
argv[arg0++] = kept_0;
257+
argv[arg0] = argv[argc+1];
258+
}
259+
return status;
260+
}
261+
190262
static int external_grep(struct grep_opt *opt, const char **paths, int cached)
191263
{
192264
int i, nr, argc, hit, len, status;
@@ -253,22 +325,12 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
253325
push_arg(p->pattern);
254326
}
255327

256-
/*
257-
* To make sure we get the header printed out when we want it,
258-
* add /dev/null to the paths to grep. This is unnecessary
259-
* (and wrong) with "-l" or "-L", which always print out the
260-
* name anyway.
261-
*
262-
* GNU grep has "-H", but this is portable.
263-
*/
264-
if (!opt->name_only && !opt->unmatch_name_only)
265-
push_arg("/dev/null");
266-
267328
hit = 0;
268329
argc = nr;
269330
for (i = 0; i < active_nr; i++) {
270331
struct cache_entry *ce = active_cache[i];
271332
char *name;
333+
int kept;
272334
if (!S_ISREG(ntohl(ce->ce_mode)))
273335
continue;
274336
if (!pathspec_matches(paths, ce->name))
@@ -283,10 +345,10 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
283345
argv[argc++] = name;
284346
if (argc < MAXARGS && !ce_stage(ce))
285347
continue;
286-
status = exec_grep(argc, argv);
348+
status = flush_grep(opt, argc, nr, argv, &kept);
287349
if (0 < status)
288350
hit = 1;
289-
argc = nr;
351+
argc = nr + kept;
290352
if (ce_stage(ce)) {
291353
do {
292354
i++;
@@ -296,7 +358,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
296358
}
297359
}
298360
if (argc > nr) {
299-
status = exec_grep(argc, argv);
361+
status = flush_grep(opt, argc, nr, argv, NULL);
300362
if (0 < status)
301363
hit = 1;
302364
}

t/t7002-grep.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ do
107107
diff expected actual
108108
'
109109

110+
test_expect_failure "grep -c $L (no /dev/null)" '
111+
git grep -c test $H | grep -q "/dev/null"
112+
'
113+
110114
done
111115

112116
test_done

0 commit comments

Comments
 (0)